Skip to content

Default to bitrouter/auto, and read the catalog off the real wire - #6

Merged
SPIKESPIGEL404 merged 3 commits into
mainfrom
claude/auto-route-and-wire-mapping
Aug 24, 2026
Merged

Default to bitrouter/auto, and read the catalog off the real wire#6
SPIKESPIGEL404 merged 3 commits into
mainfrom
claude/auto-route-and-wire-mapping

Conversation

@SPIKESPIGEL404

@SPIKESPIGEL404 SPIKESPIGEL404 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

What

1. bitrouter/auto leads every catalog, and is the default model. The request carries bitrouter/auto as its model and BitRouter's routing policy picks per request. The config hook names it as both model and small_model, so a fresh opencode.json carrying nothing but "plugin": ["@bitrouter/opencode"] is a complete installation.

??= is the whole of the courtesy: a model the user wrote themselves, or another plugin set first, is left exactly as it stands. Routing the small-model errands through auto is what the auto route is for — BitRouter's own ladder is a better judge of "cheap enough for this" than a hardcoded second model id.

bitrouter/auto is the default, not the only option — the rest of the catalog is registered behind it.

Before authentication on cloud the provider is now seeded with the auto route alone rather than a kimi-k2.5 placeholder; without at least one model the provider would not be selectable and /connect would be unreachable.

2. The discovery field mapping was wrong against both data planes.

Neither plane sends context_window, a flat cost object, or boolean reasoning/tool_call fields — which is what this plugin was reading:

Read Cloud actually sends Result before
context_window max_input_tokens every model at the 128K default
cost.* pricing (nested, per-million) every model priced at zero
reasoning / tool_call capabilities tokens always false / always true
providers: string[] { total_online } typed wrong

Verified against the live endpoints: all 50 cloud models now carry true context windows (1M for Fable 5, 200K for Opus 4.6, Haiku's 8192 output cap) where previously every one fell back to the default.

Absent capability tokens read as "the plane did not say" rather than "no" — the local daemon never sends them, and a coding agent is unusable against a model it believes cannot call tools.

test/wire.test.ts pins the mapping against bodies captured verbatim from both planes, so a field rename upstream fails a test rather than silently defaulting a model.

Placeholder capacities

Until BitRouter's catalog lists auto, the entry is synthesized at 128K context / 16K output. Those are deliberately the floor rather than the ceiling: auto may land on any model in the ladder, and the two wrong answers do not cost the same — under-claiming compacts a session early, over-claiming fails a request outright, mid-turn. A served auto entry always wins, so real capacities arrive with no release here.

Verified end to end ✅

Against a live BitRouter daemon carrying the reserved-namespace build, with an auto preset bound to a routing policy:

Result
Catalog discovery 13 models off the real wire
bitrouter/auto offered yes, leading the catalog
Default selection bitrouter/auto
Real completion ✅ routed through the policy to the economy tier

The model id is bitrouter/auto ⚠️ (corrected)

An earlier revision of this branch used a bare auto. That was wrong: BitRouter reserves the whole bitrouter/ namespace for itself (RESERVED_NAMESPACE) and bitrouter/auto is the public slug (AUTO_SLUG) — the vendor segment names the router being addressed, not the token destination. A bare auto gets 404 no active provider declares model 'auto'.

Two consequences, both now reflected in the code and docs:

  • GET /v1/models will never list this id. The namespace resolves before any provider lookup, and the registry validator refuses catalog models under bitrouter/ — so the entry has to be synthesized here. The "once the catalog lists it" framing in the first revision was wrong.
  • It needs a preset named auto bound to a routing policy. Without one the gateway answers 400 naming bitrouter optimize setup. Worth knowing before this ships to users, since a fresh install has no such preset.

The reserved-namespace support is not in the released binary — homebrew 1.0.0-alpha.26 returns 404 for it; alpha.27 dev builds resolve it. That, not this branch, is the real release gate.

Tests

45 → 63, including the new wire.test.ts fixtures.

🤖 Generated with Claude Code

**The auto route.** `bitrouter/auto` hands model choice back to BitRouter: the
request carries `auto` as its model and the gateway's policy picks per request.
It leads every catalog the plugin produces, and the `config` hook names it as
`model` and `small_model` — so a fresh opencode.json carrying nothing but
`"plugin": ["@bitrouter/opencode"]` is a complete installation.

`??=` is the whole of the courtesy: a model the user wrote themselves, or
another plugin set first, is left exactly as it stands. Routing the small-model
errands through `auto` is what the auto route is for — BitRouter's own ladder
is a better judge of "cheap enough for this" than a hardcoded second model id.

It is the default, not the only option: the rest of the catalog is registered
behind it. A served `auto` entry always wins over the synthesized one, so the
real capacities arrive with no release here.

Before authentication on cloud the provider is now seeded with the auto route
alone rather than a `kimi-k2.5` placeholder — without at least one model the
provider would not be selectable and `/connect` would be unreachable.

**The discovery field mapping.** Neither data plane sends `context_window`, a
flat `cost` object, or boolean `reasoning`/`tool_call` fields — which is what
this plugin was reading. Cloud sends `max_input_tokens`, nested per-million
`pricing`, and `capabilities` tokens; the local daemon sends `{id, object,
providers}` and nothing else. Every cloud model was therefore shown at the 128K
default and priced at zero, a 1M-context model included.

Absent capability tokens read as "the plane did not say" rather than "no": the
local daemon never sends them, and a coding agent is unusable against a model
it believes cannot call tools.

test/wire.test.ts pins the mapping against bodies captured verbatim from both
planes, so a field rename upstream fails a test rather than silently defaulting
a model.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SPIKESPIGEL404 and others added 2 commits August 23, 2026 18:09
`tsconfig.test.json` is stricter than the source project and is its own CI
step. `modalities` and `limit` are optional on opencode's config-model type, so
reading through them needs `?.` — the assertion is unchanged either way, since
an undefined left-hand side still fails the comparison.

The plugin.test.ts ProviderBlock helper gains the `limit` it now asserts on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BitRouter reserves the whole `bitrouter/` namespace for itself
(`RESERVED_NAMESPACE` in `crates/bitrouter-sdk/src/config/presets.rs`) and
`bitrouter/auto` is the public slug for policy-driven routing (`AUTO_SLUG`).
The vendor segment names the router being addressed, not the token
destination. A bare `auto` is not that id: the gateway answers 404 `no active
provider declares model 'auto'`, because nothing resolves it before the
provider lookup.

Verified end to end against a daemon carrying the reserved-namespace build:
`bitrouter/auto` completes and the response echoes the public slug, while the
request is fulfilled by whichever tier the bound policy picks.

Two claims went with it. The namespace is resolved before any provider lookup
and BitRouter's registry validator refuses catalog models under `bitrouter/`,
so `GET /v1/models` will never list this id — the entry has to come from here,
and the "once the catalog lists it" framing was wrong. The synthesized-entry
check is kept anyway: it costs nothing and stops the placeholder shadowing a
served entry if one ever appears.

The id still resolves only where a preset named `auto` is bound to a routing
policy; without one the gateway answers 400 naming `bitrouter optimize setup`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@SPIKESPIGEL404
SPIKESPIGEL404 merged commit 824d783 into main Aug 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant